<?php
/**
 * @brief Record class.
 * 
 * Supported record types:
 * \li A
 * \li AAAA
 * \li CNAME
 * \li MX
 * \li NS
 * \li SOA
 * \li TXT
 *
 * @license		http://www.gnu.org/licenses/gpl-3.0.txt GNU General Public License
 * @copyright	2008 David Singer
 * @author		David Singer <david@ramaboo.com>
 * @version		1.0.0
 */



class Record {
	protected $id = 0;
	protected $zone = false;
	protected $ttl = 86400;
	protected $type = false;
	protected $host = '@';
	protected $mx_priority = 0;
	protected $data = '';
	protected $primary_ns = '';
	protected $resp_contact = '';
	protected $serial = 0;
	protected $refresh = 0;
	protected $retry = 0;
	protected $expire = 0;
	protected $minimum = 0;
	protected $user_id = 0; // implement
	
	
	protected static $supported_types = array(
		'A',
		'AAAA',
		'CNAME',
		'MX',
		'NS',
		'SOA',
		'TXT'
	);
	
	public function __construct() {}
	
	public static function getSupportedTypes() { return self::$supported_types; }
	
	public static function isSupportedType($type) {
		$type = strtoupper(trim($type));
		return in_array($type, self::$supported_types);
	}
	
	public static function isRespContact($resp_contact) {
		$resp_contact = strtolower(trim($resp_contact));
		
		if ($resp_contact == 'hostmaster') {
			// whitelist
			return true;
		}
	
		
		///////////////////////// fix
		return true;
	}
	
	public static function isIPv4($ip) {
		
		return true;
	}
	
	public static function isIPv6($ip) {
		
		return true;
	}
	
	public static function isIP($ip) {
		return self::isIPv4($ip) || self::isIPv6($ip);
	}
	
	public static function isZone($zone) {
		$zone = strtolower(trim($zone));
		
		$len = strlen($zone);
		if ($len < 1) {
			// zone cannot be less than 1 character
			return false;
		}
		
		if ($len > 255) {
			// zone cannot be over 255 characters
			return false;
		}
		
		if (strpos($zone, '.') === false) {
			// zone must contain a period
			return false;
		}
		
		// passed all the checks
		return true;
	}
	
	public static function isHost($host) {
		//////////////////////////////////////////// fix
		return true;
		
	
	}
	
	
	
	
	
	public static function fixDot($data) {
		$data = strtolower(trim($data));
		
		
		// whitelist
		if ($data == 'hostmaster') {
			return $data;
		}
	

		//FIXXxxxxxxxxxxxx
		// see http://www.zytrax.com/books/dns/apa/dot.html
		$last_char = substr($data, -1); // last character
		
		if ($last_char == '.') {
			// has trailing dot
			return $data;
		} else {
			// add trailing dot
			return $data . '.';
		}
	}
	
	public function getId() { return $this->id; }
	
	public function setZone($zone) {
		$zone = strtolower(trim($zone));
		if (self::isZone($zone)) {
			$this->zone = $zone;
			return true;
		} else {
			throw new Exception("Zone $zone is not valid.");
			return false;
		}
	}
	
	public function getZone() { return $this->zone; }
	
	public function setTTL($ttl) {
		$ttl = intval($ttl);
		// check if ttl is valid
		if ($ttl >= 0 && $ttl <= 2147483647) {
			// ttl is positive and less than max int size
			$this->ttl = $ttl;
			return true;
		} else {
			throw new Exception("TTL $ttl is not valid.");
			return false;
		}
	}
	
	public function getTTL() { return $this->ttl; }
	
	public function setType($type) {
		$type = strtoupper(trim($type));
		// check if type is valid
		if (self::isSupportedType($type)) {
			$this->type = $type;
			return true;
		} else {
			throw new Exception("Type $type is not valid.");
			return false;
		}
	}
	
	public function getType() { return $this->type; }
	
	public function setHost($host) {
		$host = strtolower(trim($host));
		// check if host is valid
		if (self::isHost($host)) {
			$this->host = $host;
			return true;
		} else {
			throw new Exception("Host $host is not valid.");
			return false;
		}
	}
	
	public function getHost() { return $this->host; }
	
	public function setMXPriority($mx_priority) {
		$mx_priority = intval($mx_priority);
		// check if mx priority is valid
		if ($mx_priority >= 0 && $mx_priority <= 65535) {
			// ttl is positive and less than small int size
			$this->mx_priority = $mx_priority;
			return true;
		} else {
			throw new Exception("MX priority $mx_priority is not valid.");
			return false;
		}
	}
	
	public function getMXPriority() { return $this->mx_priority; }
	
	public function setData($data) {
		$data = trim($data);
		
		if (strlen($data) > 255) {
			throw new Exception("Data $data is not valid, 255 character limit reached.");
			return false;
		}
	
		////////////// switch on type
		
		
		// FIX
		
		$this->data = $data;
	
	}
	
	public function getData() { return $this->data; }
	
	public function setPrimaryNS($primary_ns) {
		$primary_ns = strtolower(trim($primary_ns));
		
		// ###################3 fix
		
		$this->primary_ns = $primary_ns;
	
	}
	
	public function getPrimaryNS() { return $this->primary_ns; }
	
	public function setRespContact($resp_contact) {
		$resp_contact = strtolower(trim($resp_contact));
		// replace @ with period
		$resp_contact = str_replace('@', '.', $resp_contact);
		
		$resp_contact = self::fixDot($resp_contact);
		
		if (self::isRespContact($resp_contact)) {
			$this->resp_contact = $resp_contact;
			return true;
		} else {
			throw new Exception("Resp contact $resp_contact is not valid.");
			return false;
		}
	}
	
	public function getRespContact() { return $this->$resp_contact; }
	
	public function setSerial($serial) {
		$serial = (int) trim($serial);
		
		if (self::isSerial($serial)) {
			$this->serial = $serial;
			return true;
		} else {
			throw new Exception("Serial $serial is not valid.");
			return false;
		}
	}
	
	public function getSerial() { return $this->serial; }
	
	
	public static function isSerial($serial) {
		
		
		
		if ($serial >= 1 && $serial <= 2147483647) {
			// in the correct range
			
			return true;
		} else {
			return false;
		}
	/*	
		
		if (substr($serial, 0, 8) != date('Ymd')) {
			// not todays date
			return false;
		}
		
		if (!preg_match('/^[0-9]{2}$/', substr($serial, 8, 2))) {
			// last 2 digits are not 01 to 99
			return false;
		}
		
		// passed
		return true;
*/
	}
	
	public function autoSerial() {
		$serial = date('Ymd', time()) . '01';
		$this->serial = $serail;
		return true;
	}
	
	public function incrementSerial() {
		$serial = $this->serial;
		$serial++;
		if (self::isSerial($serial)) {
			$this->serial = $serial;
		} else {
			throw new Exception("Serial $serial is no longer valid");
			return false;
		}
		
	}
	
	
	public function setRefresh($num) {
		$num = intval($num);
		
		if ($num >= 0 && $num <= 2147483647) {
			$this->refresh = $num;
			return true;
		} else {
			throw new Exception("Refresh $num is not valid.");
			return false;
		}
	}
	
	public function getRefresh() { return $this->refresh; }
	
	public function setRetry($num) {
		$num = intval($num);
		
		if ($num >= 0 && $num <= 2147483647) {
			$this->retry = $num;
			return true;
		} else {
			throw new Exception("Retry $num is not valid.");
			return false;
		}
	}
	
	public function getRetry() { return $this->retry; }
	
	public function setExpire($num) {
		$num = intval($num);
		
		if ($num >= 0 && $num <= 2147483647) {
			$this->expire = $num;
			return true;
		} else {
			throw new Exception("Expire $num is not valid.");
			return false;
		}
	}
	
	public function getExpire() { return $this->expire; }
	
	public function setMinimum($num) {
		$num = intval($num);
		
		if ($num >= 0 && $num <= 2147483647) {
			$this->minimum = $num;
			return true;
		} else {
			throw new Exception("Minimum $num is not valid.");
			return false;
		}
	}
	
	public function getMinimum() { return $this->minimum; }
	
	
	// this function does any nessesary checks before a record is saved
	private function beforeSave() {
		
	
	}
	public function remove() {
		if ($this->id == null) {
			throw new Exception("Record is not open.");
			return false;
		}
	
		
	
		$dbh = DB::connect();	
		$stmt = $dbh->prepare("DELETE FROM resource_records WHERE id = :id");
		$stmt->bindParam(':id', $this->id);
		
		try {
			$stmt->execute();
			
		
		} catch (Exception $e) {
			trigger_error($e->getMessage(), E_USER_ERROR);
			return false;
		}
		
		return true;
	
	}
	
	// save record
	public function save() {
		
		
		
		if (!$this->isValid()) {
			throw new Exception("Record $this->id is not valid");
			return false;
		}
		
		$dbh = DB::connect();
		if ($this->id == 0) {
			// add new record
			$stmt = $dbh->prepare("INSERT INTO resource_records (
				zone, 
				ttl, 
				type, 
				host, 
				mx_priority, 
				data, 
				primary_ns,
				resp_contact,
				serial,
				refresh,
				retry,
				expire,
				minimum
				) VALUES (
				:zone, 
				:ttl, 
				:type, 
				:host, 
				:mx_priority, 
				:data, 
				:primary_ns,
				:resp_contact,
				:serial,
				:refresh,
				:retry,
				:expire,
				:minimum
			)");
			
			$stmt->bindParam(':zone', $this->zone);
			$stmt->bindParam(':ttl', $this->ttl);
			$stmt->bindParam(':type', $this->type);
			$stmt->bindParam(':host', $this->host);
			$stmt->bindParam(':mx_priority', $this->mx_priority);
			$stmt->bindParam(':data', $this->data);
			$stmt->bindParam(':primary_ns', $this->primary_ns);
			$stmt->bindParam(':resp_contact', $this->resp_contact);
			$stmt->bindParam(':serial', $this->serial);
			$stmt->bindParam(':refresh', $this->refresh);
			$stmt->bindParam(':retry', $this->retry);
			$stmt->bindParam(':expire', $this->expire);
			$stmt->bindParam(':minimum', $this->minimum);
			
			try {
				$stmt->execute();
			} catch (Exception $e) {
				trigger_error($e->getMessage(), E_USER_ERROR);
				return false;
			}
			// set record id
			$this->id = $dbh->lastInsertId(); 
			
		} else {
			// update existing record
			$stmt = $dbh->prepare("UPDATE resource_records SET 
				zone = :zone, 
				ttl = :ttl, 
				type = :type, 
				host = :host, 
				mx_priority = :mx_priority, 
				data = :data, 
				primary_ns = :primary_ns,	
				resp_contact = :resp_contact,
				serial = :serial,
				refresh = :refresh,
				retry = :retry,
				expire = :expire,
				minimum = :minimum
			WHERE id = :id LIMIT 1");
			
			$stmt->bindParam(':id', $this->id);
			$stmt->bindParam(':zone', $this->zone);
			$stmt->bindParam(':ttl', $this->ttl);
			$stmt->bindParam(':type', $this->type);
			$stmt->bindParam(':host', $this->host);
			$stmt->bindParam(':mx_priority', $this->mx_priority);
			$stmt->bindParam(':data', $this->data);
			$stmt->bindParam(':primary_ns', $this->primary_ns);
			$stmt->bindParam(':resp_contact', $this->resp_contact);
			$stmt->bindParam(':serial', $this->serial);
			$stmt->bindParam(':refresh', $this->refresh);
			$stmt->bindParam(':retry', $this->retry);
			$stmt->bindParam(':expire', $this->expire);
			$stmt->bindParam(':minimum', $this->minimum);
			
			try {
				$stmt->execute();
			} catch (Exception $e) {
				trigger_error($e->getMessage(), E_USER_ERROR);
				return false;
			}
			
		}
		return true;
	}
	
	public function open($id) {
		// make it an int
		$id = intval($id);
		
		if ($id == 0) {
			throw new Exception("Record id is zero");
			return false;
		}
		
		$dbh = DB::connect();
		$stmt = $dbh->prepare("SELECT * FROM resource_records WHERE id = :id LIMIT 1");
		
		$stmt->bindParam(':id', $id);
		
		$stmt->bindColumn('id', $this->id);
		$stmt->bindColumn('zone', $this->zone);
		$stmt->bindColumn('ttl', $this->ttl);
		$stmt->bindColumn('type', $this->type);
		$stmt->bindColumn('host', $this->host);
		$stmt->bindColumn('mx_priority', $this->mx_priority);
		$stmt->bindColumn('data', $this->data);
		$stmt->bindColumn('primary_ns', $this->primary_ns);
		$stmt->bindColumn('resp_contact', $this->resp_contact);
		$stmt->bindColumn('serial', $this->serial);
		$stmt->bindColumn('refresh', $this->refresh);
		$stmt->bindColumn('retry', $this->retry);
		$stmt->bindColumn('expire', $this->expire);
		$stmt->bindColumn('minimum', $this->minimum);
		
		try {
			$stmt->execute();
			$result = $stmt->fetch(PDO::FETCH_BOUND);
			if (!$result) {
				throw new Exception("Record id $id is not valid.");
				return false;
			} else {
				// record is open
			
			}
		} catch (Exception $e) {
			trigger_error($e->getMessage(), E_USER_ERROR);
			return false;
		}
		
		return true;
	}
	
	public function close() {
		// do cleanup
		return true;
	}
	
		
	public function htmlDebug() {
		$tmp = '';
		$tmp .= "<h1>Record</h1>";
		$tmp .= "<ul>";
		$tmp .= "<li><b>id:</b> $this->id</li>";
		$tmp .= "<li><b>zone:</b> $this->zone</li>";
		$tmp .= "<li><b>ttl:</b> $this->ttl</li>";
		$tmp .= "<li><b>type:</b> $this->type</li>";
		$tmp .= "<li><b>host:</b> $this->host</li>";
		$tmp .= "<li><b>mx_data:</b> $this->mx_data</li>";
		$tmp .= "<li><b>primary_ns:</b> $this->primary_ns</li>";
		$tmp .= "<li><b>resp_contact:</b> $this->resp_contact</li>";
		$tmp .= "<li><b>serial:</b> $this->serial</li>";
		$tmp .= "<li><b>refresh:</b> $this->refresh</li>";
		$tmp .= "<li><b>retry:</b> $this->retry</li>";
		$tmp .= "<li><b>expire:</b> $this->expire</li>";
		$tmp .= "<li><b>minimum:</b> $this->minimum</li>";
		$tmp .= "</ul>";
		return $tmp;
	}
	
	public function isValid() {
		// FFFFFFFFFFFFFFF fix
		// do lots of checks to make sure hte record is valid
		// switch type
		return true;
	
	}
	
	public static function exists($search, $column) {
		$column = strtolower(trim($column));
		
		// check column for SQL injections
		switch ($column) {
			// safe values
			case 'id':
			case 'zone':
			case 'ttl':
			case 'type':
			case 'host':
			case 'mx_priority':
			case 'data':
			case 'primary_ns':
			case 'resp_contact':
			case 'serial':
			case 'refresh':
			case 'retry':
			case 'expire':
			case 'minimum':
			case 'user_id':
				// do nothing, everything is OK
				break;
			default:
				throw new Exception("Column $column is not valid, SQL injection detected.");
				exit;
		}
		
		$id = 0;
		$dbh = DB::connect();
		
		$stmt = $dbh->prepare("SELECT id FROM resource_records WHERE $column = :search LIMIT 1");
		$stmt->bindParam(':search', $search);
		$stmt->bindColumn('id', $id);
			
		try {
			$stmt->execute();
			$result = $stmt->fetch(PDO::FETCH_BOUND);
		} catch (Exception $e) {
			trigger_error($e->getMessage(), E_USER_ERROR);
			return false;
		}
		
		return $id;
	}
	
}

?>